实用Shader

实用的一些 Shader,语雀迁移

2D Shader

nightyan/Shader2D

CSDN说明地址

之后如果没有什么事情的话应该一天更新一个讲解

github 下载可能会比较慢或者下不下来,这里放到百度云上应该比较容易下载, 提取码:pw1u 。稍微改了一点之前的BUG,虽然有不少警告,不过至少看 Shader 效果是够了。

该项目的缺点以及改进

  • 另外,这个项目面板的位移和缩放,即 offset 和 tiling 是不可用的,如果需要只用在 vert 中修改一行代码就好了,即把下面的注释掉,加上上面的一行就可以了。
1
2
o.texcoord.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
//o.texcoord = v.texcoord;
  • IN.color默认就是白色的,想要加上颜色调整的话,可以在面板上加个属性,然后在 vert 中给 o.color 赋值,之后在 frag 中进行了乘法的话就会实现对应的效果。

其中可以分下类,这里可以先总体说明一下,可能和目录不是很对应,大体理解一下就好。

  • Filter 用来算和周边八个位置的颜色的一些差异等。

  • 有一些是通过固定的公式计算的,比如老照片效果、灰化效果、调整饱和度,都是有相关的数学公式,然后再转换成代码。

  • 另外 UV 和 COLOR 的取值都是从0到1,COLOR 是 RGB 通道的。所以有时候会看到1-或者取绝对值的操作,前者可能是为了把偏白色变成偏黑色,后者可能是计算后有负数然后转换一下。

  • 待总结

Filter 滤波操作

Blur 模糊

原理: 采样附近上下左右四个相邻像素的颜色,与当前像素颜色按比例混合(简单滤波)

这是最简单的滤波操作,只进行了上下左右四个位置上的颜色混合,没有使用到滤波函数,后面的进阶用到了,不过对于理解滤波也有帮助。vert 里面只是常规的操作,在 frag 函数中,把当前片元的颜色与上下左右四个位置的颜色进行了混合,然后输出,上下左右加上本身的颜色分别占比0.2。

Shader 中 有个 uvOffset,没有暴露在面板上,是通过脚本控制来实现这个值的赋值,进而控制模糊程度。Shader.SetGlobalFloat(“uvOffset”, (clearTime/length) * 0.005f); 原项目里是使用的这个API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
Shader "Custom/Blur" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader
{
Tags{"Queue"="Transparent"}

pass
{
Cull Off

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
float uvOffset;

struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};

v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}

float4 frag (v2f i) : COLOR
{
float4 s1 = tex2D(_MainTex, i.uv + float2(uvOffset,0.00));
float4 s2 = tex2D(_MainTex, i.uv + float2(-uvOffset,0.00));
float4 s3 = tex2D(_MainTex, i.uv + float2(0.00,uvOffset));
float4 s4 = tex2D(_MainTex, i.uv + float2(0.00,-uvOffset));

float4 texCol = tex2D(_MainTex, i.uv);
float4 outp;
float pct = 0.2;
outp = texCol * (1- pct*4) + s1* pct + s2* pct+ s3* pct + s4* pct;
return outp;
}
ENDCG
}
}
}

Blur进阶,以及锐化

BlurBox 效果: box模糊 原理: 采样周边8个相邻像素的颜色,与当前像素颜色按平均比例混合(Box滤波器)BlurGauss 效果: 高斯模糊 原理: 采样周边8个相邻像素的颜色,与当前像素颜色按比例混合(高斯滤波器)Sharpen 效果: 拉普拉斯锐化 原理: 先将自身与周围的8个象素相减,表示自身与周围象素的差别,再将这个差别加上自身作为新象素的颜色

这三种和 Blur 一样都是通过和周围颜色进行一些混合后进行的操作可以看到 filter 方法都一样,都是对周围八个方向进行颜色采样,通过不同的3x3矩阵进行配比相加,对应的3x3矩阵应该是通过数学计算得到的,不过也很容易理解,模糊就是周围颜色按比例混合使之模糊,锐化就是相减计算与周围颜色差异,差异越大边缘越明显。可以把 filter 滤波方法提取出来,滤波方法在第二个代码块中。

三个3x3矩阵分别是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
float3x3 boxFilter = 
{
1.0f/9, 1.0f/9, 1.0f/9,
1.0f/9, 1.0f/9, 1.0f/9,
1.0f/9, 1.0f/9, 1.0f/9,
};
Box模糊

float3x3 gaussFilter =
{
1.0f/16, 2.0f/16, 1.0f/16,
2.0f/16, 4.0f/16, 2.0f/16,
1.0f/16, 2.0f/16, 1.0f/16,
};
高斯模糊

float3x3 laplaceFilter =
{
-1, -1, -1,
-1, 9, -1,
-1, -1, -1,
};
拉普拉斯锐化

三段代码都有两个 SubShader,其中第二个 LOD 100应该作为备用的,只贴出了对应的关键部分代码。LOD的使用方法是,找到小于等于当前 Shader LOD
最大的那一个 SubShader 执行。

这里只放出一个的核心部分作为参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Shader "Custom/BlurBox"

//对图像做滤波操作
float4 filter(float3x3 filter, sampler2D tex, float2 coord, float2 texSize)
{
float4 outCol = float4(0,0,0,0);
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
//计算采样点,得到当前像素附近的像素的坐标
float2 newCoord = float2(coord.x + (i-1)*_BlurOffset, coord.y + (j-1)*_BlurOffset);
float2 newUV = float2(newCoord.x / texSize.x, newCoord.y / texSize.y);
//采样并乘以滤波器权重,然后累加
outCol += tex2D(tex, newUV) * filter[i][j];
}
}
return outCol;
}

fixed4 frag (v2f IN) : COLOR
{
float3x3 boxFilter =
{
1.0f/9, 1.0f/9, 1.0f/9,
1.0f/9, 1.0f/9, 1.0f/9,
1.0f/9, 1.0f/9, 1.0f/9,
};

float2 coord = float2(IN.uv.x * _TexSize.x, IN.uv.y * _TexSize.y);
return filter(boxFilter, _MainTex, coord, _TexSize);
}

Pencil 效果: 铅笔画描边

Pencil 效果: 铅笔画描边 原理: 如果在图像的边缘处,灰度值肯定经过一个跳跃,我们可以计算出这个跳跃,并对这个值进行一些处理,来得到边缘浓黑的描边效果,就像铅笔画一样。

Pencil 用到了滤波方法,用来突出图像边缘,一般和周围片元进行一些操作的都会用到滤波,比如模糊、锐化、突出边缘等。这里先使用了 pencilFilter 来进行滤波,根据矩阵的分布可以看到是减去左上加上左下,是通过对比左上和左下的差异度得出边界。然后使用了灰度因子进行了灰度转换,然后进行了取绝对值和1-的操作,进行灰度转换是为了让颜色看起来统一,取绝对值并用1-操作是为了转换成为白色背景和黑色线条,否则效果相反。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fixed4 frag (v2f IN) : COLOR
{
float3x3 pencilFilter =
{
-0.5, -1.0, 0.0,
-1.0, 0.0, 1.0,
0.0, 1.0, 0.5
};

float2 coord = float2(IN.uv.x * _TexSize.x, IN.uv.y * _TexSize.y);
float4 filterCol = filter(pencilFilter, _MainTex, coord, _TexSize);
float gray = 0.3 * filterCol.x + 0.59 * filterCol.y + 0.11 * filterCol.z;
gray = abs(gray);
gray = 1.0 - gray;
return float4(gray, gray, gray, 1);
}

Emboss 浮雕 以及 Gray 灰化

浮雕原理: 图像的前景前向凸出背景。把象素和左上方的象素进行求差运算,并加上一个灰度(背景)。 灰化原理:进行灰度公式计算。

先讲解浮雕的计算,主要代码在 frag 中

  • 第一步是进行了与左上方像素的求差运算,这样就把边缘颜色突出出来了,不是边缘的颜色就基本为0,黑色。
1
2
3
float2 leftUpUV = float2(IN.uv.x - 1/_TexSize.x, IN.uv.y - 1/_TexSize.y);
fixed4 leftUpCol = tex2D(_MainTex, leftUpUV);
fixed4 deffCol = col - leftUpCol;
  • 第二步是进行了背景填充和灰度算法,就是把边缘进行了灰度处理

    • 先是进行了边缘的 dot 点积运算,就是与灰度因子进行了乘法,使之颜色相对变灰。然后进行了背景填充,把非边缘的纯黑区域填充成了灰色,而原来的边缘部分也相应更加突出了。所谓的灰度因子也就是(0.3,0.59,0.11)吧(我猜的。。或者说个人理解)。

1
2
3
4
5
fixed4 outCol;
outCol.rgb = dot(deffCol, fixed3(0.3, 0.59, 0.11));
outCol = outCol + fixed4(0.5, 0.5, 0.5, 0);
outCol.a = col.a;
return outCol;

这样灰化的原理就很简单了,只有一句关键代码col.rgb = dot(col.rgb, fixed3(0.3, 0.59, 0.11)); 进行了灰度公式计算。

Mosaic 效果: 马赛克

原理: n x n方块内取同一颜色

这里是采用了取整的方式实现了 nxn 方块内取同一颜色并没有判断每一个方块是否在对应的范围内,先是用从0到1的 UV 坐标值乘以贴图的长度避免取整后都为1了,再除以单个马赛克方格的边长这样除以同一个范围内的就会取得同一个整数值,最后用这个整数值除以贴图长度,就得到了对应位置的 UV 坐标值,然后通过 UV 值取得坐标,就得到了对应马赛克方格的颜色。

1
2
3
4
5
6
7
8
fixed4 frag (v2f IN) : COLOR
{
float pixelX = int(IN.uv.x * _TexSize.x / _SquareWidth) * _SquareWidth;
float pixelY = int(IN.uv.y * _TexSize.y / _SquareWidth) * _SquareWidth;
float2 uv = float2(pixelX / _TexSize.x, pixelY / _TexSize.y);
fixed4 col = tex2D(_MainTex, uv);
return col;
}

OldPhoto 效果: 老照片

OldPhoto 效果: 老照片 原理: r = 0.393r + 0.769g + 0.189b; g = 0.349r + 0.686g + 0.168b; b = 0.272r + 0.534g + 0.131*b;

对于 OldPhtoto,关键代码在 frag 中,代码很简单,就是套用了上面说的原理,RGB 分别做了不同的颜色获得操作,展示出老照片的效果。

1
2
3
4
5
6
7
8
9
10
11
fixed4 frag (v2f IN) : COLOR
{
fixed4 col = tex2D(_MainTex, IN.texcoord);
fixed r = 0.393*col.r + 0.769*col.g + 0.189*col.b;
fixed g = 0.349*col.r + 0.686*col.g + 0.168*col.b;
fixed b = 0.272*col.r + 0.534*col.g + 0.131*col.b;
col.r = r;
col.g = g;
col.b = b;
return col;
}

Saturation 效果: 调整饱和度

原理: RGB转HSL,增加S再转回RGB

RGB 和 HSL 分别是颜色的不同标准,RGB是通过红绿蓝三个通道来表示;而 HLS 是用另三个通道来表示,Hue 色度, Lightness 亮度, Saturation 饱和度。

具体关于 RGB、HSL、HSB 的区别和转换可以参考这个答案 色彩空间中的HSL、HSV、HSB有什么区别?下面的代码应该就是对公式进行了代码化,加上用户对饱和度的调整最后输出新的 RGB 三通道的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
float4 frag (v2f i) : COLOR
{
float4 col = tex2D(_MainTex, i.uv);
float rgbmax = max(col.r, max(col.g, col.b));
float rgbmin = min(col.r, min(col.g, col.b));
float delta = rgbmax - rgbmin;
if (delta == 0)
return col;

float value = (rgbmax + rgbmin);
float light = value / 2;
float cmp = step(light, 0.5);
float sat = lerp(delta/(2-value), delta/value, cmp);
if (_SatIncrement >= 0)
{
cmp = step(1, _SatIncrement + sat);
float a = lerp(1-_SatIncrement, sat, cmp);
a = 1/a - 1;
col.r = col.r + (col.r - light) * a;
col.g = col.g + (col.g - light) * a;
col.b = col.b + (col.b - light) * a;
}
else
{
float a = _SatIncrement;
col.r = light + (col.r - light) * (1+a);
col.g = light + (col.g - light) * (1+a);
col.b = light + (col.b - light) * (1+a);
}
return col;
}

HDR 效果

原理: 让亮的地方更亮,同时为了过渡更平滑柔和,亮度采用高斯模糊后的亮度(灰度值)

这里只是套用了一个公式,简单的 2D HDR 效果。其中用了个高斯模糊后的纹理,是通过面板选择进去的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 调整亮度,让亮的地方更亮  公式: y = x * [(2-4k)*x + 4k-1]
float4 hdr(float4 col, float gray, float k)
{
float b = 4*k - 1;
float a = 1 - b;
float f = gray * ( a * gray + b);
return f * col;
}

fixed4 frag (v2f IN) : COLOR
{
float4 col = tex2D(_MainTex, IN.uv);
float4 blurCol = tex2D(_BlowTex, IN.uv);// 高斯模糊后的纹理,让过渡更柔和、平滑
float gray = 0.3 * blurCol.r + 0.59 * blurCol.g + 0.11 * blurCol.b;
//float gray = 0.3 * col.r + 0.59 * col.g + 0.11 * col.b;
return hdr(col, gray, _Param);
}

背景滚动(一般是作为移动场景背景表现使用)

  • 效果展示

  • 使用注意事项

    • 图片资源 要设置成 Repeat 模式,不然滚动会出现拖影的错误。

    • 可以把 Sprite 放在 Camera 下,然后放大到填充整个摄像机范围,就可以很好实现远景滚动效果。

    • 要把图片拖到材质上。

    • 可以直接使用 Sprite GO,也可以新建类似 Quad 的 mesh 类型的 GO。 mesh 相关文章可以在理论部分查到,简单的说 mesh 包括了一个模型或者Sprite或者一个UI(即所有展示出来的东西)的所有信息。

  • 原理简单地通过调用时间参数移动了 uv 的位置。

frac 是取小数的意思,float2 的 y 值为0,所以只会平移水平方向,竖直方向不会变,这个应该是为了实现循环滚动,只用从0到1的部分,不过具体 UV 的取值范围还没有详细去了解。 UV的取值范围是从(0,0)到(1,1)

// Transforms 2D UV by scale/bias property

#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)
配合使用时的代码:
o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
其中
v是appdata_base类型,v.texcoord就是模型顶点的uv数据。
_MainTex是使用的图片。
namexx_ST实际上就是_MainTex_ST。
namexx_ST.xy就是Tiling的xy值。
namexx_ST.zw就是Offset的xy值。

作者:HelloMingo
来源:CSDN
原文:https://blog.csdn.net/u010133610/article/details/78789940
版权声明:本文为博主原创文章,转载请附上博文链接!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Shader "Unity Shaders Book/Chapter 11/Scrolling Background" {
Properties {
_MainTex ("Base Layer (RGB)", 2D) = "white" {}
_ScrollX ("Base layer Scroll Speed", Float) = 1.0
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}

Pass {
Tags { "LightMode"="ForwardBase" }

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

sampler2D _MainTex;
float4 _MainTex_ST;
float _ScrollX;

struct a2v {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};

struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};

v2f vert (a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex) + frac(float2(_ScrollX, 0.0) * _Time.y);

return o;
}

fixed4 frag (v2f i) : SV_Target {
fixed4 firstLayer = tex2D(_MainTex, i.uv.xy);

return firstLayer;
}

ENDCG
}
}
FallBack "VertexLit"
}

背景滚动V0.0.2(根据输入进行滚动以及不规则图片的滚动问题)

0%